package org.idea.qiyu.framework.gateway.core.core;

import org.idea.qiyu.framework.gateway.starter.registry.ApplicationChangeEvent;
import org.idea.qiyu.framework.gateway.starter.registry.ApplicationRegistry;
import org.idea.qiyu.framework.gateway.starter.registry.URL;
import org.idea.qiyu.framework.gateway.core.utils.GatewayLocalCache;
import org.jboss.netty.util.internal.ThreadLocalRandom;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationListener;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 单例对象
 *
 * @Author linhao
 * @Date created in 8:55 上午 2022/4/13
 */
@WebServlet(name = "GatewayServlet", urlPatterns = "/*")
public class GatewayCoreServlet extends HttpServlet implements InitializingBean, ApplicationListener<ApplicationChangeEvent> {

    private RestTemplate restTemplate = new RestTemplate();


    @Resource
    private ApplicationRegistry applicationRegistry;

    private static GatewayLocalCache gatewayLocalCache = new GatewayLocalCache();

    @Override
    public void init() {
        List<URL> urls = applicationRegistry.getRegistryInfo();
        if (urls.size() == 0) {
            return;
        }
        Map<String, List<URL>> map = urls.stream().collect(Collectors.groupingBy(URL::getApplicationName));
        GatewayLocalCache.gatewayLocalCache = map;
    }

    /**
     * 网关的核型请求都会被路由到这里的service函数中，然后在这里进行http的转发
     * 请求进入到servlet内部之后会有线程安全问题，另外注意下：每个servlet在tomcat内部是单例的存在
     *
     * @param req
     * @param resp
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String targetURL = req.getRequestURI();
        if (StringUtils.isEmpty(targetURL)) {
            return;
        }
        if ("/favicon.ico".equals(targetURL)) {
            return;
        }
        Map<String, String> tempMap = MatchApplicationInfo.buildTempMapping();
        String applicationName = null;
        //匹配前缀
        for (String mappingStr : tempMap.keySet()) {
            if (targetURL.contains(mappingStr)) {
                applicationName = tempMap.get(mappingStr);
                break;
            }
        }
        if (applicationName == null) {
            return;
        }
        List<URL> urls = GatewayLocalCache.get(applicationName);
        if (urls == null || urls.size() == 0) {
            return;
        }
        int index = new ThreadLocalRandom().nextInt(urls.size());
        URL url = urls.get(index);
        String prefix = "http://" + url.getAddress() + ":" + url.getPort();
        targetURL = prefix + targetURL;
        String method = req.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        //1、封装请求头
        MultiValueMap<String, String> headers = createRequestHeaders(req);
        //2、封装请求体
        byte[] body = createRequestBody(req);
        //3、构造出RestTemplate能识别的RequestEntity
        RequestEntity requestEntity = null;
        try {
            requestEntity = new RequestEntity<byte[]>(body, headers, httpMethod, new URI(targetURL));
            ResponseEntity responseEntity = restTemplate.exchange(requestEntity, byte[].class);
            Object respB = responseEntity.getBody();
            if (respB != null) {
                byte[] respByte = (byte[]) respB;
                resp.setCharacterEncoding("UTF-8");
                resp.setHeader("content-type", "application/json;charset=UTF-8");
                resp.getWriter().print(new String(respByte,"UTF-8"));
                resp.getWriter().flush();
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private byte[] createRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream servletInputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(servletInputStream);
    }

    private MultiValueMap<String, String> createRequestHeaders(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<String> headerNames = Collections.list(request.getHeaderNames());
        for (String headerName : headerNames) {
            String headerVal = request.getHeader(headerName);
            headers.put(headerName, Collections.singletonList(headerVal));
        }
        return headers;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        applicationRegistry.subscribeURL();
    }

    @Override
    public void onApplicationEvent(ApplicationChangeEvent applicationChangeEvent) {
        System.out.println(">>>>>>>>> [applicationChangeEvent] >>>>>>>>> ");
        this.init();
    }
}
